package com.smile.frame.websocket.netty.http.server;


import cn.hutool.core.convert.Convert;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.MD5;
import com.smile.frame.websocket.netty.http.stream.CameraDto;
import com.smile.frame.websocket.netty.http.stream.MediaService;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.CharsetUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.util.Iterator;
import java.util.Map;

/**
 * 请求处理器
 * 在线 连接 ws 地址 https://wstool.js.org/
 *
 * @author LGC
 */
@Slf4j
@Component
@RequiredArgsConstructor
@ChannelHandler.Sharable //不new，采用共享handler
public class RequestHandler extends SimpleChannelInboundHandler<Object> {

    private final MediaService mediaService;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("新增连接");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("关闭连接");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("连接异常", cause);
        ctx.close();
    }

    /**
     * 通道读取
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {

        if (msg instanceof FullHttpRequest) {
            log.info("处理http请求");
            FullHttpRequest req = (FullHttpRequest) msg;
            // 请求解析
            QueryStringDecoder decoder = new QueryStringDecoder(req.uri());
            log.info("【HttpRequest-PATH:" + decoder.path() + "】");
            log.info("【HttpRequest-URI:" + decoder.uri() + "】");
            log.info("【HttpRequest-Parameters:" + decoder.parameters() + "】");
            log.info("【HttpRequest-URI:" + req.uri() + "】");
            log.info("【HttpRequest-Method:" + req.method().name() + "】");

            Iterator<Map.Entry<String, String>> iterator = req.headers().iteratorAsString();
            while (iterator.hasNext()) {
                Map.Entry<String, String> entry = iterator.next();
                log.info("【Header-Key:" + entry.getKey() + ";Header-Value:" + entry.getValue() + "】");
            }
            // http请求及非转换升级为ws请求
            if (!req.decoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))) {
                log.info("http请求 响应构建");
                // 判断请求uri 是否是转流指定url
                if ("/live".equals(decoder.path())) {
                    HttpResponse rsp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
                    rsp.headers()
                            .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE)
                            .set(HttpHeaderNames.CONTENT_TYPE, "video/x-flv").set(HttpHeaderNames.ACCEPT_RANGES, "bytes")
                            .set(HttpHeaderNames.PRAGMA, "no-cache").set(HttpHeaderNames.CACHE_CONTROL, "no-cache")
                            .set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED).set(HttpHeaderNames.SERVER, "netty-http");
                    ctx.writeAndFlush(rsp);
                    CameraDto cameraDto = buildCamera(req.uri());
                    mediaService.playForHttp(cameraDto, ctx);
                } else {
                    ByteBuf content = Unpooled.copiedBuffer("netty-http 你好", CharsetUtil.UTF_8);
                    FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
                    response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=utf-8");
                    response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
                    ctx.writeAndFlush(response);
                }
            }
            // http请求升级为ws
            else {
                // 升级为ws握手
                upgradeWsHandshaker(ctx, req, decoder);
                // 判断请求uri 是否是转流指定url
                if ("/live".equals(decoder.path())) {
                    CameraDto cameraDto = buildCamera(req.uri());
                    mediaService.playForWs(cameraDto, ctx);
                }
            }
        }

        if (msg instanceof WebSocketFrame) {
            log.info("处理ws请求消息");
            WebSocketFrame webSocketFrame = (WebSocketFrame) msg;
            handlerWebSocketRequest(ctx, webSocketFrame);
        }
    }


    /**
     * 解析参数，构建camera，&&&参数必须加在url参数值后面，&&&autoClose=true
     * ws://localhost:8866/live?url=rtsp://admin:fangjia301@192.168.88.22:554/Streaming/Channels/1&&&autoClose=false
     */
    private CameraDto buildCamera(String url) {
        CameraDto cameraDto = new CameraDto();
        cameraDto.setNetTimeout("15000000");
        cameraDto.setReadOrWriteTimeout("15000000");
        cameraDto.setAutoClose(true);
        cameraDto.setNoClientsDuration(60000);

        String[] split = url.split("url=");
        String urlParent = split[1];

        String[] split2 = urlParent.split("&&&");
        if (split2.length > 0) {
            for (String string : split2) {
                if (string.indexOf("autoClose=") != -1) {
                    String[] as = string.split("=");
                    if (as.length <= 1) {
                        throw new RuntimeException("autoClose参数有误");
                    }
                    cameraDto.setAutoClose(Convert.toBool(as[1], false));
                } else if (string.indexOf("ffmpeg=") != -1) {
                    String[] as = string.split("=");
                    if (as.length <= 1) {
                        throw new RuntimeException("ffmpeg参数有误");
                    }
                    cameraDto.setEnabledFFmpeg(Convert.toBool(as[1], false));
                } else if (string.indexOf("hls=") != -1) {
                    String[] as = string.split("=");
                    if (as.length <= 1) {
                        throw new RuntimeException("hls参数有误");
                    }
                    cameraDto.setEnabledHls(Convert.toBool(as[1], false));
                } else {
                    cameraDto.setUrl(string);
                }
            }
        }

        if (isLocalFile(cameraDto.getUrl())) {
            cameraDto.setType(1);
        }

        // 区分不同媒体
        cameraDto.setMediaKey(SecureUtil.md5(cameraDto.getUrl()));

        return cameraDto;
    }

    /**
     * 是否是本地文件,判断前面长度是不是小于1个字符，认为是盘符
     */
    private boolean isLocalFile(String streamUrl) {
        String[] split = streamUrl.trim().split("\\:");
        if (split.length > 0) {
            if (split[0].length() <= 1) {
                return true;
            }
        }
        return false;
    }

    /**
     * 升级为ws握手
     */
    private static void upgradeWsHandshaker(ChannelHandlerContext ctx, FullHttpRequest req, QueryStringDecoder decoder) {
        // 参考摘自 WebSocketServerProtocolHandshakeHandler
        log.info("websocket握手，请求升级");
        log.info("getWebSocketLocation:{}", getWebSocketLocation(ctx.pipeline(), req, decoder.path()));
        // 参数分别是ws地址，子协议，是否扩展，最大frame负载长度
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
                getWebSocketLocation(ctx.pipeline(), req, decoder.path()),
                null, true, 3 * 1024 * 1024);
        final WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req);
        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            // 握手
            HttpResponse rsp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
            rsp.headers().set(HttpHeaderNames.SERVER, "netty-http");
            ChannelPromise channelPromise = ctx.channel().newPromise();
//                    ChannelPromise channelPromise = new DefaultChannelPromise(ctx.channel());
            final ChannelFuture handshakeFuture = handshaker.handshake(ctx.channel(), req, rsp.headers(), channelPromise);
            handshakeFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) {
                    if (!future.isSuccess()) {
                        channelPromise.tryFailure(future.cause());
                        log.info("握手失败");
                        ctx.fireExceptionCaught(future.cause());
                    } else {
                        channelPromise.trySuccess();
                        log.info("握手成功");
                        // 发送握手成功事件，后面handler可监听
                        // Kept for compatibility
//                                ctx.fireUserEventTriggered(
//                                        WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE);
//                                ctx.fireUserEventTriggered(
//                                         new WebSocketServerProtocolHandler.HandshakeComplete(
//                                                req.uri(), req.headers(), handshaker.selectedSubprotocol()));
                    }
                }
            });
        }
    }

    /**
     * 摘自 WebSocketServerProtocolHandshakeHandler
     */
    private static String getWebSocketLocation(ChannelPipeline cp, HttpRequest req, String path) {
        String protocol = "ws";
        if (cp.get(SslHandler.class) != null) {
            // SSL in use so use Secure WebSockets
            protocol = "wss";
        }
        String host = req.headers().get(HttpHeaderNames.HOST);
        return protocol + "://" + host + path;
    }

    /**
     * 处理ws 消息
     */
    private static void handlerWebSocketRequest(ChannelHandlerContext ctx, WebSocketFrame frame) {
        // 文本消息 (约定 与客户端 Socket 消息类型为文本消息)
        if (frame instanceof TextWebSocketFrame) {
            log.info("文本消息");
            TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
            log.info("收到来自客户端消息: {}", textFrame.text());
            if (textFrame.text().equals("PING")) {
                ctx.writeAndFlush(new TextWebSocketFrame("PONG"));
            } else {
                ctx.writeAndFlush(new TextWebSocketFrame("服务端发送的消息 hello wold"));
            }
        }
        // 握手PING/PONG
        if (frame instanceof PingWebSocketFrame) {
            log.info("PING消息");
            ctx.writeAndFlush(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        // 二进制信息
        if (frame instanceof BinaryWebSocketFrame) {
            log.info("二进制信息");
            return;
        }

        // 请求关闭连接类型消息
        if (frame instanceof CloseWebSocketFrame) {
            log.info("关闭ws信息");
            ctx.channel().close();
        }
    }

}
